import numpy as np #导入NumPy数学工具箱
import pandas as pd #导入Pandas数据处理工具箱
# 读入数据并显示前面几行的内容，这是为了确保我们的文件读入正确性
# 示例代码是在Kaggle中数据集中读入文件，如果在本机中需要指定具体本地路径

def shop_demo():
    df_ads = pd.read_csv('advertising.csv')
    df_ads.head()
    X = np.array(df_ads)  # 构建特征集，含全部特征
    X = np.delete(X, [3], axis=1)  # 删除掉标签
    y = np.array(df_ads.sales)  # 构建标签集，销售金额
    print("张量X的阶:", X.ndim)
    print("张量X的形状:", X.shape)
    print(X)

    y = y.reshape(-1, 1)  # 通过reshape函数把向量转换为矩阵，-1就是len(y),返回样本个数
    print("张量y的形状:", y.shape)

    # 将数据集进行80%（训练集）和20%（验证集）的分割
    from sklearn.model_selection import train_test_split
    X_train, X_test, y_train, y_test = train_test_split(X, y,
    test_size = 0.2, random_state = 0)

    y_min, y_max, y_gap = min_max_gap(y_train)

    X_train_original = X_train.copy()  # 保留一份训练集数据副本，用于对要预测数据归一化
    X_train, X_test = scaler(X_train, X_test)  # 对特征归一化
    y_train, y_test = scaler(y_train, y_test)  # 对标签也归一化

    x0_train = np.ones((len(X_train), 1))  # 构造X_train长度的全1数组配合对Bias的点积
    X_train = np.append(x0_train, X_train, axis=1)  # 把X增加一系列的1
    x0_test = np.ones((len(X_test), 1))  # 构造X_test长度的全1数组配合对Bias的点积
    X_test = np.append(x0_test, X_test, axis=1)  # 把X增加一系列的1
    print("张量X的形状:", X_train.shape)
    print(X_train)

    # 首先确定参数的初始值
    iterations = 300;  # 迭代300次
    alpha = 0.15;  # 学习速率设为0.15
    weight = np.array([0.5, 1, 1, 1])  # 权重向量，w[0] = bias
    # 计算一下初始值的损失
    print('当前损失：', loss_function(X_train, y_train, weight))

    # 调用刚才定义的线性回归模型
    loss_history, weight_history = linear_regression(X_train, y_train,
                                                     weight, alpha, iterations)  # 训练机器

    print("权重历史记录：", weight_history)
    print("损失历史记录：", loss_history)

    X_plan = [250, 50, 50]  # 要预测的X特征数据
    X_train, X_plan = scaler(X_train_original, X_plan)  # 对预测数据也要归一化缩放
    X_plan = np.append([1], X_plan)  # 加一个哑特征X0 = 1 在多元线性回归中，权重b=b*1=W0*W0
    y_plan = np.dot(weight_history[-1], X_plan)  # [-1] 即模型收敛时的权重
    # 对预测结果要做反向缩放，才能得到与原始广告费用对应的预测值
    y_value = y_plan * y_gap + y_min  # y_gap是当前y_train中最大值和最小值的差，y_min是最小值
    print("预计商品销售额： ", y_value, "千元")
    print("预测权重: {}", weight_history[-1])


def scaler(train, test): # 定义归一化函数 ，进行数据压缩
    # 数据的压缩
    min = train.min(axis=0) # 训练集最小值
    max = train.max(axis=0) # 训练集最大值
    gap = max - min # 最大值和最小值的差
    train -= min # 所有数据减最小值
    train /= gap # 所有数据除以大小值差
    test -= min #把训练集最小值应用于测试集
    test /= gap #把训练集大小值差应用于测试集
    return train, test # 返回压缩后的数据

def min_max_gap(train): # 计算训练集最大，最小值以及他们的差，用于后面反归一化过程
    min = train.min(axis=0) # 训练集最小值
    max = train.max(axis=0) # 训练集最大值
    gap = max - min # 最大值和最小值的差
    return min, max, gap


def loss_function(X, y, W): # 手工定义一个MSE均方误差函数,W此时是一个向量
    y_hat = X.dot(W.T) # 点积运算 h(x)=w_0*x_0 + w_1*x_1 + w_2*x_2 + w_3*x_3
    loss = y_hat.reshape((len(y_hat),1))-y # 中间过程,求出当前W和真值的差异
    cost = np.sum(loss**2)/(2*len(X)) # 这是平方求和过程, 均方误差函数的代码实现
    return cost # 返回当前模型的均方误差值

def gradient_descent(X, y, W, lr, iterations): # 定义梯度下降函数
    l_history = np.zeros(iterations) # 初始化记录梯度下降过程中损失的数组
    W_history = np.zeros((iterations,len(W))) # 初始化权重数组
    for iter in range(iterations): # 进行梯度下降的迭代，就是下多少级台阶
        y_hat = X.dot(W.T) # 这个是向量化运行实现的假设函数
        loss = y_hat.reshape((len(y_hat),1))-y # 中间过程, y_hat和y真值的差
        derivative_W = X.T.dot(loss)/len(X) #求出多项式的梯度向量
        derivative_W = derivative_W.reshape(len(W))
        W = W - lr*derivative_W # 结合下降速率更新权重
        l_history[iter] = loss_function(X, y, W) # 损失的历史记录
        W_history[iter] = W # 梯度下降过程中权重的历史记录
    return l_history, W_history # 返回梯度下降过程数据

# 定义线性回归模型
def linear_regression(X, y, weight, alpha, iterations):
    loss_history, weight_history = gradient_descent(X, y,
                                                    weight,
                                                    alpha, iterations)
    print("训练最终损失:", loss_history[-1]) # 打印最终损失
    y_pred = X.dot(weight_history[-1]) # 进行预测
    traning_acc = 100 - np.mean(np.abs(y_pred - y))*100 # 计算准确率
    print("线性回归训练准确率: {:.2f}%".format(traning_acc))  # 打印准确率
    return loss_history, weight_history # 返回训练历史记录

if __name__ == '__main__':
    shop_demo();